package com.mgy.db.sharding.aop;

import com.mgy.db.TableRouter;
import com.mgy.db.sharding.annotation.HintIgnore;
import com.mgy.db.sharding.annotation.MybatisMapper;
import com.mgy.db.sharding.utils.DbUtil;
import org.apache.shardingsphere.api.hint.HintManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;

/**
 * 拦截dao层数据库操作，使用hint强制分片
 *
 * @author mgy
 * @date 2019.09.03
 */
@Aspect
@Configuration
public class MapperAspect {

    private static final Logger logger = LoggerFactory.getLogger(MapperAspect.class);

    @Autowired(required = false)
    private IShardingCallback shardingCallback;

    /**
     * 切点
     * execution(public * com.mgy.db.dao.mapper..*(..)) || @within(com.mgy.db.sharding.annotation.MybatisMapper)
     */
    @Pointcut("@within(com.mgy.db.sharding.annotation.MybatisMapper)")
    public void mapperPointcut() {

    }

    @Around("mapperPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Class type = method.getDeclaringClass();
        String[] parameterNames = methodSignature.getParameterNames();
        Class[] parameterTypes = methodSignature.getMethod().getParameterTypes();
        Object[] parameterValues = joinPoint.getArgs();
        Annotation[][] paramAnnotations = method.getParameterAnnotations();
        if (parameterNames == null && parameterTypes != null && parameterTypes.length > 0 && method.getParameters() != null) {
            Parameter[] parameters = method.getParameters();
            parameterNames = new String[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                parameterNames[i] = parameters[i].getName();
            }
        }
        MybatisMapper mybatisMapper = AnnotationUtils.findAnnotation(type, MybatisMapper.class);
        try {
            if (!method.isAnnotationPresent(HintIgnore.class) && !type.isAnnotationPresent(HintIgnore.class)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("parameterNames=" + (parameterNames == null ? null : String.join(",", parameterNames)));
                }
                Set<TableRouter> tableRouterList = this.getTableRouters(method, parameterNames, parameterTypes, parameterValues, mybatisMapper);
                if (tableRouterList == null) {
                    tableRouterList = DbUtil.getTableRouters(parameterNames, parameterTypes, parameterValues, paramAnnotations);
                }

                if (!CollectionUtils.isEmpty(tableRouterList)) {
                    if (tableRouterList.size() == 1) {
                        TableRouter tableRouter = tableRouterList.iterator().next();
                        if (StringUtils.isEmpty(tableRouter.getLogicTable()) && mybatisMapper != null && StringUtils.hasText(mybatisMapper.table())) {
                            tableRouter.setLogicTable(mybatisMapper.table());
                        }
                    }
                    //只分库，不分表的特殊处理
                    tableRouterList = DbUtil.parseTableRouter(tableRouterList);

                    if (logger.isDebugEnabled()) {
                        logger.debug("分表信息:tableRouterListSize=" + tableRouterList.size());
                    }
                    HintManager hintManager = HintManager.getInstance();
                    for (TableRouter router : tableRouterList) {
                        if (router.getDbNo() != null) {
                            if (StringUtils.hasText(router.getLogicTable()) && router.getTableNo() != null) {
                                hintManager.addDatabaseShardingValue(router.getLogicTable(), router);
                                hintManager.addTableShardingValue(router.getLogicTable(), router);
                            } else if (StringUtils.hasText(router.getLogicTable()) && router.getTableNo() == null) {
                                hintManager.addDatabaseShardingValue(router.getLogicTable(), router);
                            } else {
                                //分库不分表
                                hintManager.setDatabaseShardingValue(router);
                            }
                        }
                    }
                }
            }
            //执行业务方法
            return joinPoint.proceed();
        } finally {
            HintManager.clear();
        }

    }


    private Set<TableRouter> getTableRouters(Method method, String[] parameterNames, Class[] parameterTypes, Object[] parameterValues, MybatisMapper mybatisMapper) {
        if (shardingCallback != null) {
            return shardingCallback.getTableRouters(method, parameterNames, parameterTypes, parameterValues, mybatisMapper);
        }

        //SPI
        ServiceLoader<IShardingCallback> serviceLoader = ServiceLoader.load(IShardingCallback.class);
        Iterator<IShardingCallback> iterator = serviceLoader.iterator();
        if (iterator.hasNext()) {
            IShardingCallback shardingCallback = iterator.next();
            return shardingCallback.getTableRouters(method, parameterNames, parameterTypes, parameterValues, mybatisMapper);
        }
        return null;
    }

}
